<template>
  <el-upload
    ref="uploadRef"
    v-bind="{ ...$attrs, ...filteredAttrs }"
    :file-list="innerFileList"
    :on-change="handleChange"
    :on-remove="handleRemove"
  >
    <template v-for="slot in Object.keys($slots).filter(name => name !== 'tip')" #[slot]="scope">
      <slot :name="slot" v-bind="scope" />
    </template>
    <template #tip="scope">
      <!--   将进度给到父组件   -->
      <slot name="tip" v-bind="{ ...scope, progress }" />
    </template>
  </el-upload>
</template>

<script setup lang="ts">
import { ElUpload, UploadFile, UploadFiles, type UploadProps, UploadUserFile } from "element-plus";
import { computed, ref, watch } from "vue";
import { CHUNK_SIZE, chunkInit, chunkUploadFile, CONCURRENCY, uploadFile } from "@/api/modules/file/FileDetail";

const uploadRef = ref<InstanceType<typeof ElUpload>>();
const progress = ref<number>(0);
// 内部维护的文件列表
const innerFileList = ref<UploadUserFile[]>([]);

interface UploadFileProps {
  // 上传的平台，这是必须的
  platform: string;
  // 是否启用分片
  chunk?: boolean;
}

// 合并 ElUpload 的 props 和自定义 props
const props = defineProps<Partial<UploadProps> & UploadFileProps>();
const emit = defineEmits(["update:file-list", "submit-complete"]);

// 获取所有未定义的属性
// const attrs = useAttrs();
// 过滤出 ElUpload 支持的属性（避免传递未知属性导致警告）
const filteredAttrs = computed(() => {
  // 移除自定义 props（如 chunk），避免传递给 ElUpload
  const { chunk, ...rest } = { ...props };
  // 分片上传一次只能上传一个文件
  if (chunk) {
    rest.multiple = false;
    rest.limit = 1;
  }
  // 提交url无需父组件定义，统一上传到后端的file模块中，业务开发只需要拿着fileId后端自己去获取文件即可
  rest.action = undefined;
  rest.httpRequest = handleUpload;
  return rest;
});

// 监听外部 fileList 变化（父组件可能通过 v-model 更新）
watch(
  () => props.fileList,
  newList => {
    if (newList) {
      innerFileList.value = newList;
    }
  },
  { deep: true }
);

// 文件变化时更新内部列表并通知父组件
const handleChange = (uploadFile: UploadFile, uploadFiles: UploadFiles) => {
  innerFileList.value = uploadFiles;
  // 同步给父组件
  emit("update:file-list", uploadFiles);
};

const handleRemove = (uploadFile: UploadFile, uploadFiles: UploadFiles) => {
  innerFileList.value = uploadFiles;
  // 同步给父组件
  emit("update:file-list", uploadFiles);
};

const handleUpload = async () => {
  if (props.chunk) {
    // 大文件分片上传
    return [await handleChunkUpload()];
  }
  // 普通上传
  if (innerFileList.value.length == 0) {
    return [];
  }
  let formData = new FormData();
  formData.append("platform", props.platform);
  innerFileList.value.forEach(file => {
    if (file.raw) {
      formData.append("files", file.raw);
    }
  });
  let { data } = await uploadFile(formData);
  progress.value = 100;
  return data;
};

interface Chunk {
  chunkNumber: number;
  chunk: Blob;
}

const handleChunkUpload = async () => {
  let file = innerFileList.value[0];
  if (!file.raw) {
    // 如果文件不存在怎么上传呢？
    return {};
  }
  let fileSize = file.size || file.raw.size;
  // 先初始化分片上传
  let { data } = await chunkInit({
    platform: props.platform,
    originalName: file.name,
    size: fileSize
  });
  const totalChunks = Math.ceil(fileSize / CHUNK_SIZE);
  let uploadedChunks = 0;
  // 先进行分片
  const chunks: Chunk[] = [];
  let chunkNumber = 1;
  for (let i = 0; i < fileSize; i += CHUNK_SIZE) {
    chunks.push({
      chunkNumber: chunkNumber++,
      chunk: file.raw.slice(i, i + CHUNK_SIZE)
    });
  }
  // 开始分片上传
  for (let i = 0; i < chunks.length; i += CONCURRENCY) {
    let group: Promise<any>[] = [];
    for (let chunk of chunks.slice(i, i + CONCURRENCY)) {
      let formData = new FormData();
      formData.append("partFile", chunk.chunk);
      formData.append("chunkNumber", chunk.chunkNumber.toString());
      formData.append("fileId", data.id);
      group.push(
        chunkUploadFile(formData)
          .then(() => {
            console.log(`分片[${chunk.chunkNumber}] 上传成功!`);
            uploadedChunks++;
            progress.value = Math.round((uploadedChunks / totalChunks) * 100);
          })
          .catch(err => {
            console.error(`分片[${chunk.chunkNumber}] 上传失败: `, err);
          })
      );
    }
    await Promise.all(group);
  }
  // 这里不需要手动调用合并，因为后端会自动进行合并(根据上传的大小)
  return data;
};

defineExpose({
  // 将uploadRef的方法传递出去
  uploadRef,
  handleUpload
});
</script>

<style scoped lang="scss"></style>
